#include <limits>
#include <algorithm>

#include "meshgrid.h"

MeshGrid::MeshGrid()
{
    vao = 0;
    vbo = 0;
    ibo = 0;
    count = 0;
    tfbo = 0;
    feedbackBool = false;
}

vmath::vec3 MeshGrid::getMinPosition()const
{
    return minPos;
}

vmath::vec3 MeshGrid::getMaxPosition()const
{
    return maxPos;
}

void MeshGrid::createMesh(GLfloat *vertices, unsigned int *indices, unsigned int numOfVertices,
                      unsigned int numOfIndices)
{
    count = numOfIndices;

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices[0]) * numOfIndices, indices, GL_STATIC_DRAW);

    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices[0]) * numOfVertices, vertices, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vertices[0]) * 8, 0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertices[0]) * 8,
                          (void *)(sizeof(vertices[0]) * 3));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(vertices[0]) * 8,
                          (void *)(sizeof(vertices[0]) * 5));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glBindVertexArray(0);

    minPos = vmath::vec3(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(),
                         std::numeric_limits<float>::max());
    maxPos = vmath::vec3(std::numeric_limits<float>::min(), std::numeric_limits<float>::min(),
                         std::numeric_limits<float>::min());

    for (unsigned int i = 0; i < numOfIndices / 8; i++) {
        float x = vertices[8 * i + 0];
        float y = vertices[8 * i + 1];
        float z = vertices[8 * i + 2];
        minPos[0] = std::min<float>(x, minPos[0]);
        minPos[1] = std::min<float>(y, minPos[1]);
        minPos[2] = std::min<float>(z, minPos[2]);
        maxPos[0] = std::max<float>(x, maxPos[0]);
        maxPos[1] = std::max<float>(y, maxPos[1]);
        maxPos[2] = std::max<float>(z, maxPos[2]);
    }
}

void MeshGrid::renderMesh()
{
    glBindVertexArray(vao);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

    if (useGlpatch) {
        glDrawElements(GL_PATCHES, count, GL_UNSIGNED_INT, 0);
    } else {
        if (feedbackBool) {
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tfbo);
            glBeginTransformFeedback(GL_TRIANGLES);
            glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, 0);
            glEndTransformFeedback();
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);

            glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tfbo);
            vmath::vec4 *tfboData = static_cast<vmath::vec4 *>(glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                                                                                count * static_cast<int64_t>(sizeof(vmath::vec4)), GL_MAP_READ_BIT));
            feedbackOut.clear();
            feedbackOut.reserve(static_cast<GLuint>(count));
            for (uint i = 0; i < static_cast<GLuint>(count); i++) {
                feedbackOut.push_back(tfboData[i]);
            }
            glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
            glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
        } else {

            glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, 0);
        }

    }

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void MeshGrid::clearMesh()
{
    if (ibo != 0) {
        glDeleteBuffers(1, &ibo);
        ibo = 0;
    }

    if (vbo != 0) {
        glDeleteBuffers(1, &vbo);
        vbo = 0;
    }

    if (vao != 0) {
        glDeleteVertexArrays(1, &vao);
        vao = 0;
    }
    if (tfbo != 0) {
        glDeleteBuffers(1, &tfbo);
        tfbo = 0;
    }
    count = 0;
}

void MeshGrid::setUseGLPath(unsigned int glpath)
{
    useGlpatch = glpath;
}

MeshGrid::~MeshGrid()
{
    clearMesh();
}

void MeshGrid::makeTfbo()
{
    if (tfbo == 0) {
        glGenBuffers(1, &tfbo);
    }
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tfbo);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, count * static_cast<int64_t>(sizeof(vmath::vec4)), \
                 nullptr, GL_DYNAMIC_COPY);
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, 0);
}

void MeshGrid::enableFeedback()
{
    feedbackBool = true;
    makeTfbo();
}

std::vector<vmath::vec4> MeshGrid::getFeedbackOut()
{
    return feedbackOut;
}
